#include <CL/opencl.h>
#include "clError.h"

#ifndef __HELPER
#define HELPER

#define MAX_SOURCE_SIZE 1000000
#define MAX_INFO_SIZE 128


/* 
 * Get the first device of type device_type found.
 */
cl_device_id opencl_get_device_id(cl_device_type device_type) {

    cl_platform_id *clPlatforms;      // OpenCL platform
    cl_device_id device_id;           // device ID
    cl_int err;

    // Bind to platform
    cl_uint num_platforms = 0;
    err = clGetPlatformIDs(0, NULL, &num_platforms);
    printf("%d platforms found\n",num_platforms);
    clPlatforms = (cl_platform_id *) malloc(num_platforms*sizeof(cl_platform_id));
    err = clGetPlatformIDs(num_platforms, clPlatforms, NULL);
    if (CL_SUCCESS != err) {
       fprintf(stderr,"Error! %s\n",clErrorString(err));
       exit(1);
    }
    
    int platform_id = -1;

    int i;
    for (i=0; i < num_platforms; i++) {
   
       // Get ID for the device
       err = clGetDeviceIDs(clPlatforms[i], device_type, 1, &device_id, NULL);
       if (CL_SUCCESS == err) {
          platform_id = i;
          break;
       }
    }
    if (platform_id < 0) {
       fprintf(stderr,"Error! cannot find such device\n");
       exit(1);
    }

    // Get platform info
    char info[MAX_INFO_SIZE] = {0};
    err = clGetPlatformInfo(clPlatforms[platform_id],CL_PLATFORM_VENDOR,MAX_INFO_SIZE*sizeof(char),info,NULL);
    printf("Use platform %d, %s\n",platform_id,info);

    // Get device info
    err = clGetDeviceInfo(device_id,CL_DEVICE_NAME,MAX_INFO_SIZE*sizeof(char),info,NULL);
    check_cl_error(err);
    printf("Device %s, ",info);
    err = clGetDeviceInfo(device_id,CL_DEVICE_MAX_COMPUTE_UNITS,MAX_INFO_SIZE*sizeof(char),info,NULL);
    check_cl_error(err);
    printf("%d compute units\n",*info);

    return device_id;
}

cl_kernel opencl_load_kernel_from_file(const cl_context context, const cl_device_id device_id, const int numsources, const char **sources, const char *kernel_name) {

    FILE *fp;
    size_t *source_size;
    char **source_str;
    cl_int err;
    int i;

    source_size = (size_t *) malloc(sizeof(size_t)*numsources);
    source_str = (char **) malloc(sizeof(char *)*numsources);
    for (i = 0; i < numsources; i++) {
       fp = fopen(sources[i],"r");
       if (fp == NULL) {
          fprintf(stderr,"Fail to load kernel from file %s.\n",sources[i]);
          exit(1);
       }
       source_str[i] = (char *) malloc(MAX_SOURCE_SIZE);
       source_size[i] = fread(source_str[i],1,MAX_SOURCE_SIZE,fp);
       printf("Load %s %d bytes\n",sources[i],source_size[i]);
       fclose(fp);
    }

    // Create the compute program from the source buffer
    cl_program program = clCreateProgramWithSource(context, numsources,
                            (const char **) source_str, (const size_t *) source_size, &err);
    check_cl_error(err);

    // Build the program executable
    clBuildProgram(program, 0, NULL, " -I./", NULL, NULL);

    // Get build info
    cl_build_status build_status;
    err = clGetProgramBuildInfo(program,device_id,CL_PROGRAM_BUILD_STATUS,sizeof(cl_build_status),&build_status,NULL);
    if (CL_SUCCESS != err) {
       fprintf(stderr,"Error! %s\n",clErrorString(err));
       exit(1);
    }
    if (build_status != CL_BUILD_SUCCESS) {
       char *build_log;
       size_t ret_val_size;
       err = clGetProgramBuildInfo(program, device_id, CL_PROGRAM_BUILD_LOG, 0, NULL, &ret_val_size);
       build_log = malloc(ret_val_size+1);
       err = clGetProgramBuildInfo(program, device_id, CL_PROGRAM_BUILD_LOG, ret_val_size, build_log, NULL);
       // to be carefully, terminate with \0
       // there's no information in the reference whether the string is 0 terminated or not
       build_log[ret_val_size] = '\0';

       fprintf(stderr, "%s", build_log );
       exit(1);

    }

    cl_kernel kernel = clCreateKernel(program,kernel_name,&err);
    check_cl_error(err);

    clReleaseProgram(program);

    return kernel;
}

size_t opencl_get_max_work_group_size(const cl_device_id device_id) {
    cl_int err;
    size_t size;
    err = clGetDeviceInfo(device_id,CL_DEVICE_MAX_WORK_GROUP_SIZE,sizeof(size_t),&size,NULL);
    check_cl_error(err);

    return size;
}

size_t opencl_get_wavefront_size(cl_kernel kernel, const cl_device_id device_id) {
    cl_int err;
    size_t size;
    err = clGetKernelWorkGroupInfo(kernel,device_id,CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE,sizeof(size_t),&size,NULL);
    check_cl_error(err);

    return size;
}

cl_device_id opencl_query_device_id(const cl_command_queue command_queue) {
    cl_int err;
    cl_device_id device_id;
    err = clGetCommandQueueInfo(command_queue,CL_QUEUE_DEVICE,sizeof(cl_device_id),&device_id,NULL);
    check_cl_error(err);

    return device_id;
}

#endif
